using System.Collections.Immutable;
using System.Security.Claims;
using System.Web;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Net8.Identity.Server.Models;
using Net8.Identity.Server.Services;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace Net8.Identity.Server.Controllers
{
    /// <summary>
    /// 授权中心相关接口
    /// 包含以下流程:
    /// 1. 检查访问者是否通过身份验证，如果没有，定向去登录
    /// 2. 通过身份认证的用户，是否被授予了访问权限，如果没有，定向去授权
    /// 3. 认证授权都通过了，下发令牌，回到授权验证之前访问的页面
    /// </summary>
    [ApiController]
    public class AuthorizationController : Controller
    {
        private readonly IOpenIddictApplicationManager _applicationManager;
        private readonly IOpenIddictAuthorizationManager _authorizationManager;
        private readonly IOpenIddictScopeManager _scopeManager;
        private readonly AuthorizationService _authService;
        private readonly ApplicationDbContext _dbContext;
        private readonly ILogger<AuthorizationController> _logger;

        public AuthorizationController(
            IOpenIddictApplicationManager applicationManager,
            IOpenIddictAuthorizationManager authorizationManager,
            IOpenIddictScopeManager scopeManager,
            AuthorizationService authService,
            ApplicationDbContext dbContext,
            ILogger<AuthorizationController> logger)
        {
            _applicationManager = applicationManager;
            _authorizationManager = authorizationManager;
            _scopeManager = scopeManager;
            _authService = authService;
            _dbContext = dbContext;
            _logger = logger;
        }

        /// <summary>
        /// 统一错误响应模型
        /// </summary>
        private class ErrorResponse
        {
            public string Error { get; set; }
            public string ErrorDescription { get; set; }
            public string ErrorUri { get; set; }

            public ErrorResponse(string error, string errorDescription, string errorUri = null)
            {
                Error = error;
                ErrorDescription = errorDescription;
                ErrorUri = errorUri;
            }
        }

        /// <summary>
        /// 返回JSON格式的错误响应
        /// </summary>
        private IActionResult ForbidJson(string error, string errorDescription, string errorUri = null)
        {
            var response = new ErrorResponse(error, errorDescription, errorUri);
            return Json(response);
        }

        /// <summary>
        /// 根据用户ID获取用户信息
        /// </summary>
        /// <param name="userId">用户ID</param>
        /// <returns>用户实体或null</returns>
        private async Task<User> GetUserByIdAsync(string userId)
        {
            return await _dbContext.Users
                .FirstOrDefaultAsync(u => u.Email == userId || u.UserName == userId);
        }

        /// <summary>
        /// 处理授权请求
        /// 检查访问者是否通过身份验证，如果没有，定向去登录
        /// </summary>
        /// <returns>授权结果</returns>
        /// <exception cref="InvalidOperationException">当无法检索OpenID Connect请求或找不到客户端应用程序时抛出</exception>
        [HttpGet("~/oauth/authorize"), HttpPost("~/oauth/authorize")]
        public async Task<IActionResult> Authorize()
        {
            var request = HttpContext.GetOpenIddictServerRequest() ??
                          throw new InvalidOperationException("无法检索OpenID Connect请求.");

            var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ??
                              throw new InvalidOperationException("找不到有关调用客户端应用程序的详细信息.");

            // 验证客户端应用程序的同意类型
            if (await _applicationManager.GetConsentTypeAsync(application) != ConsentTypes.Explicit)
            {
                _logger.LogWarning("客户端 {ClientId} 不支持明确同意类型", request.ClientId);
                return ForbidJson(
                    Errors.InvalidClient,
                    "只允许具有明确同意类型的客户端.");
            }

            var parameters = _authService.ParseOAuthParameters(HttpContext, new List<string> { "prompt" });
            var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            var redirectUri = _authService.BuildRedirectUrl(HttpContext.Request, parameters);

            // 检查用户是否已通过身份验证
            if (!_authService.IsAuthenticated(result, request))
            {
                _logger.LogInformation("用户未通过身份验证，重定向到登录页面");
                return Challenge(
                    properties: new AuthenticationProperties
                    {
                        RedirectUri = redirectUri
                    }, 
                    new[] { CookieAuthenticationDefaults.AuthenticationScheme });
            }

            // 处理prompt=login参数，强制重新登录
            if (request.HasPromptValue(PromptValues.Login))
            {
                _logger.LogInformation("检测到prompt=login参数，强制用户重新登录");
                await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                return Challenge(
                    properties: new AuthenticationProperties
                    {
                        RedirectUri = redirectUri
                    }, 
                    new[] { CookieAuthenticationDefaults.AuthenticationScheme });
            }

            // 检查用户是否已授权该应用
            var consentClaim = result.Principal.GetClaim(Consts.ConsentNaming);
            
            // 可能会以一种方式扩展，即同意声明将包含允许的客户端id列表.
            if (consentClaim != Consts.GrantAccessValue || request.HasPromptValue(PromptValues.Consent))
            {
                _logger.LogInformation("用户未授权该应用或需要重新授权");
                var returnUrl = HttpUtility.UrlEncode(redirectUri);
                var clientId = HttpUtility.UrlEncode(request.ClientId);
                var consentRedirectUrl = $"/Consent?returnUrl={returnUrl}&clientid={clientId}";
                return Redirect(consentRedirectUrl);
            }

            // 获取用户标识
            var sIdClaim = result.Principal.FindFirst(Claims.Subject);
            if (sIdClaim == null)
            {
                return ForbidJson(
                    Errors.InvalidRequest,
                    "用户身份信息不完整.");
            }
            
            var userId = sIdClaim.Value;
            _logger.LogInformation("为用户 {UserId} 创建访问令牌", userId);
            // 创建用户身份信息
            var identity = CreateUserIdentity(userId, request);
            
            // 设置身份资源
            await SetIdentityResources(identity, request);

            return SignIn(
                new ClaimsPrincipal(identity), 
                OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        /// <summary>
        /// 处理令牌交换请求
        /// 下发令牌，回到授权验证之前访问的页面
        /// </summary>
        /// <returns>令牌交换结果</returns>
        /// <exception cref="InvalidOperationException">当指定的授权类型不支持时抛出</exception>
        [HttpPost("~/oauth/token")]
        public async Task<IActionResult> Exchange()
        {
            var request = HttpContext.GetOpenIddictServerRequest() ??
                          throw new InvalidOperationException("无法检索OpenID Connect请求。");
            
            // 验证授权类型
            if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
            {
                throw new InvalidOperationException("指定的授权类型不受支持。");
            }

            var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            var subject = result.Principal?.GetClaim(Claims.Subject);

            // 验证用户身份
            if (string.IsNullOrEmpty(subject))
            {
                return ForbidJson(
                    Errors.InvalidGrant,
                    "无法从令牌中找到用户.");
            }
            
            Guid guid;
            if (Guid.TryParse(subject, out guid) == false)
            {
                // 返回JSON格式的错误响应
                return ForbidJson(
                    Errors.InvalidToken,
                    "无效的GUID格式。");
            }
            
            _logger.LogInformation("为用户 {Subject} 交换令牌", subject);

            // 获取并处理用户声明
            var claims = result.Principal.Claims
                .Select(claim => claim switch
                {
                    { Type: Claims.Subject } => new Claim(Claims.Subject, claim.Value, claim.ValueType, claim.Issuer),
                    { Type: Claims.Email } => new Claim(Claims.Email, claim.Value, claim.ValueType, claim.Issuer),
                    { Type: Claims.Name } => new Claim(Claims.Name, claim.Value, claim.ValueType, claim.Issuer),
                    _ => null
                })
                .Where(claim => claim != null)
                .GroupBy(claim => claim.Type)
                .Select(group => group.First())
                .ToList();

            var identity = new ClaimsIdentity(
                claims,
                authenticationType: TokenValidationParameters.DefaultAuthenticationType,
                nameType: Claims.Name,
                roleType: Claims.Role);
            
            // 从数据库中获取用户信息以确保sub claim一致性
            var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == guid || u.Email == subject);

            identity.SetClaim(Claims.Subject, subject)
                .SetClaim(Claims.Email, user.Email)
                .SetClaim(Claims.Name, user.UserName)
                .SetClaims(Claims.Role, new List<string> { "user", "admin" }.ToImmutableArray());

            identity.SetDestinations(c => AuthorizationService.GetDestinations(identity, c));

            return SignIn(
                new ClaimsPrincipal(identity), 
                OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        /// <summary>
        /// 获取用户基本信息
        /// </summary>
        /// <returns>用户信息</returns>
        [Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
        [HttpGet("~/oauth/userinfo"), HttpPost("~/oauth/userinfo")]
        public async Task<IActionResult> Userinfo()
        {
            var subject = User.GetClaim(Claims.Subject);
            Guid guid;
            if (Guid.TryParse(subject, out guid) == false)
            {
                return ForbidJson(
                    Errors.InvalidToken,
                    "无效的GUID格式。");
            }
            
            // 验证用户是否存在
            var user = await _dbContext.Users
                .Where(u => u.Id == guid)
                .FirstOrDefaultAsync();
                
            if (user == null)
            {
                return ForbidJson(
                    Errors.InvalidToken,
                    "指定的访问令牌已绑定至一个不存在的账户。");
            }

            var claims = new Dictionary<string, object>(StringComparer.Ordinal)
            {
                [Claims.Subject] = user.Id,
                [Claims.Name] = user.UserName
            };
            
            // 如果请求了email scope，则包含用户邮箱
            if (User.HasScope(Permissions.Scopes.Email))
                claims[Claims.Email] = user.Email;
            if (User.HasScope(Permissions.Scopes.Phone))
                claims[Claims.PhoneNumber] = user.Mobile;

            _logger.LogInformation("返回用户 {UserId} 的信息", user.Id);
            return Ok(claims);
        }

        /// <summary>
        /// 处理登出请求
        /// </summary>
        /// <returns>重定向到登出页面</returns>
        [HttpGet("~/oauth/logout")]
        public IActionResult Logout()
        {
            var parameters = _authService.ParseOAuthParameters(HttpContext);
            return Redirect("/logout" + QueryString.Create(parameters));
        }
        
        /// <summary>
        /// 处理登出确认请求
        /// </summary>
        /// <param name="confirm">用户确认登出标识</param>
        /// <returns>登出结果</returns>
        [HttpPost("~/oauth/logout")]
        public async Task<IActionResult> LogoutPost(string confirm)
        {
            var request = HttpContext.GetOpenIddictServerRequest();
            
            // 如果用户没有确认登出，重定向到首页
            if (confirm != "Yes")
            {
                return Redirect("/");
            }
            
            // 清除本地认证Cookie
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            
            // 构建重定向属性
            var properties = new AuthenticationProperties
            {
                RedirectUri = request?.PostLogoutRedirectUri ?? "/"
            };
            
            _logger.LogInformation("用户已登出，重定向到: {RedirectUri}", properties.RedirectUri);
            
            return SignOut(
                authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                properties: properties);
        }

        #region Private Methods

        /// <summary>
        /// 创建用户身份对象
        /// </summary>
        /// <param name="userId">用户ID</param>
        /// <param name="request">OpenID请求</param>
        /// <returns>ClaimsIdentity对象</returns>
        private ClaimsIdentity CreateUserIdentity(string userId, OpenIddictRequest request)
        {
            var identity = new ClaimsIdentity(
                authenticationType: TokenValidationParameters.DefaultAuthenticationType,
                nameType: Claims.Name,
                roleType: Claims.Role);

            Guid guid;
            Guid.TryParse(userId, out guid);
            
            // 从数据库中获取用户信息以确保sub claim一致性
            var user = _dbContext.Users.FirstOrDefault(u => u.Id == guid || u.UserName == userId);
            var subjectValue = user != null ? user.Id.ToString()  : userId;

            // 确保Subject声明与数据库中的用户名一致
            identity.SetClaim(Claims.Subject, subjectValue)
                .SetClaim(Claims.Email, user?.Email)
                .SetClaim(Claims.Name, user?.UserName)
                .SetClaims(Claims.Role, new List<string> { "user", "admin" }.ToImmutableArray());

            identity.SetScopes(request.GetScopes());
            identity.SetDestinations(c => AuthorizationService.GetDestinations(identity, c));
            
            return identity;
        }

        /// <summary>
        /// 设置身份资源
        /// </summary>
        /// <param name="identity">用户身份</param>
        /// <param name="request">OpenID请求</param>
        private async Task SetIdentityResources(ClaimsIdentity identity, OpenIddictRequest request)
        {
            var scopes = identity.GetScopes();
            var resources = await _scopeManager.ListResourcesAsync(scopes).ToListAsync();
            identity.SetResources(resources);
        }

        #endregion
    }
}